<?php

namespace tmcore\utils;

use tmcore\traits\ErrorTrait;

/**
 * 文件处理
 * @package tmcore\utils
 */
class File
{
    use ErrorTrait;

    /**
     * 判断文件或文件夹是否可写.
     * @param string $file 文件或目录
     * @return bool
     */
    public static function is_really_writable($file)
    {
        if (DIRECTORY_SEPARATOR === '/') {
            return is_writable($file);
        }
        if (is_dir($file)) {
            $file = rtrim($file, '/') . '/' . md5(mt_rand());
            if (($fp = @fopen($file, 'ab')) === false) {
                return false;
            }
            fclose($fp);
            @chmod($file, 0777);
            @unlink($file);

            return true;
        } elseif (!is_file($file) or ($fp = @fopen($file, 'ab')) === false) {
            return false;
        }
        fclose($fp);
        return true;
    }

    /**
     * 读取文件内容
     * @param string $filename 文件名
     * @return string 文件内容
     */
    public static function read_file($filename)
    {
        $content = '';
        if (function_exists('file_get_contents')) {
            @$content = file_get_contents($filename);
        } else {
            if (@$fp = fopen($filename, 'r')) {
                @$content = fread($fp, filesize($filename));
                @fclose($fp);
            }
        }
        return $content;
    }

    /**
     * 写文件
     * @param string $filename 文件名
     * @param string $write_text 文件内容
     * @param string $openmod 打开方式
     * @return boolean
     */
    public static function write_file($filename, $write_text, $openmod = 'w')
    {
        if (@$fp = fopen($filename, $openmod)) {
            flock($fp, 2);
            fwrite($fp, $write_text);
            fclose($fp);
            return true;
        } else {
            return false;
        }
    }

    /**
     * 判断 文件/目录 是否可写（取代系统自带的 is_writeable 函数）
     * @param string $file 文件/目录
     * @return boolean
     */
    public static function is_write($file)
    {
        if (is_dir($file)) {
            $dir = $file;
            if ($fp = @fopen("$dir/test.txt", 'w')) {
                @fclose($fp);
                @unlink("$dir/test.txt");
                $writeable = true;
            } else {
                $writeable = false;
            }
        } else {
            if ($fp = @fopen($file, 'a+')) {
                @fclose($fp);
                $writeable = true;
            } else {
                $writeable = false;
            }
        }
    
        return $writeable;
    }

    /**
     * 文件尺寸大小
     * @param string $dir_path
     * @return number
     */
    public static function get_dir_size($dir_path)
    {
        $size = 0;
        if (is_dir($dir_path)) {
            $handle = opendir($dir_path);
            while (false !== ($entry = readdir($handle))) {
                if ($entry != '.' && $entry != '..') {
                    if (is_dir("{$dir_path}/{$entry}")) {
                        $size += self::get_dir_size("{$dir_path}/{$entry}");
                    } else {
                        $size += filesize("{$dir_path}/{$entry}");
                    }
                }
            }
            closedir($handle);
        }
        return $size;
    }

    /**
     * 文件尺寸大小换算
     * @param Integer size_num
     * @return string
     */
    public static function size_conversion($size_num)
    {
        switch ($size_num) {
            case $size_num >= 1073741824:
                $size_str = round($size_num / 1073741824 * 100) / 100 . ' GB';
                break;
            case $size_num >= 1048576:
                $size_str = round($size_num / 1048576 * 100) / 100 . ' MB';
                break;
            case $size_num >= 1024:
                $size_str = round($size_num / 1024 * 100) / 100 . ' KB';
                break;
            default:
                $size_str = $size_num . ' Bytes';
                break;
        }
    
        return $size_str;
    }

    /**
     * 删除文件
     * @param string $path 图片路径
     * @param bool $delDir 是否删除目录
     * @return bool
     */
    public static function del_file($path, $delDir = false)
    {
        if (is_file($path)) {
            @unlink($path);
            return true;
        } else if (!is_dir($path)){
            return true;
        }
        $op = opendir($path);
        if($op) {
            while (false !== ( $item = readdir($op) )) {
                if($item == '.' || $item == '..') {
                    continue;
                }
                if(is_dir($path.'/'.$item)) {
                    self::del_file($path.'/'.$item, true);
                } else {
                    @unlink($path.'/'.$item);
                }
            }
            closedir($op);
            if ($delDir) {
                return rmdir($path.'/'.$item);
            }
        }
    }

    /**
     * 删除指定目录下的文件和文件夹
     * @param string $dir_path
     * @return bool
     */
    public static function del_dir($dir_path)
    {
        if (is_dir($dir_path)) {
            $dh = opendir($dir_path);
            while (($file = readdir($dh)) !== false) {
                if ($file != "." && $file != "..") {
                    $full_path = $dir_path . "/" . $file;
                    if (!is_dir($full_path)) {
                        @unlink($full_path);
                    } else {
                        self::del_dir($full_path);
                        rmdir($full_path);
                    }
                }
            }
            closedir($dh);
            $isEmpty = true;
            $dh = opendir($dir_path);
            while (($file = readdir($dh)) !== false) {
                if ($file != "." && $file != "..") {
                    $isEmpty = false;
                    break;
                }
            }
            return $isEmpty;
        }
    }

    /**
     * 删除指定目录下n天前的文件和文件夹
     * @param $dir
     * @param $n
     */
    public static function del_day_file($dir, $n)
    {
        if (is_dir($dir)) {
            if ($dh = opendir($dir)) {
                while (false !== ($file = readdir($dh))) {
                    if ($file != "." && $file != "..") {
                        $full_path = $dir . "/" . $file;
                        //如果不是目录
                        if (!is_dir($full_path)) {
                            //获取文件的最后修改时间，转化时间格式
                            $filedate = date("Y-m-d", filemtime($full_path));
                            //当前时间转化时间戳
                            $d1 = strtotime(date("Y-m-d"));
                            //文件的最后修改时间 转化时间戳
                            $d2 = strtotime($filedate);
                            //换算成天
                            $Days = round(($d1 - $d2) / 3600 / 24);
                            //文件最后修改天数大于传进来的天数 就删除
                            if ($Days > $n) {
                                @unlink($full_path);
                            }
                        } else {
                            //如果还是目录 递归 并判断目录count是否为2
                            self::del_day_file($full_path, $n);
                            //目录存在'.','..'，如果为空 count就是2
                            if (count(scandir($full_path)) == 2) {
                                rmdir($full_path);// 删除空目录
                            }
                        }
                    }
                }
            }
            closedir($dh);
        }
    }

    /**
     * 创建文件夹
     * @param string $path 目录路径
     * @param int $mode 目录权限
     * @param bool $recursive 规定是否设置递归模式
     * @return bool
     */
    public static function mk_dir($path = '', $mode = 0755, $recursive = true)
    {
        clearstatcache();
        if (!is_dir($path)) {
            mkdir($path, $mode, $recursive);
            return chmod($path, $mode);
        }
    
        return true;
    }

    /**
     * 文件夹文件拷贝
     * @param string $src 来源文件夹路径
     * @param string $dst 目的文件夹路径
     * @return bool
     */
    public static function copy_dir($src = '', $dst = '')
    {
        if (empty($src) || empty($dst)) {
            return false;
        }
        $dir = opendir($src);
        self::mk_dir($dst);
        while (false !== ($file = readdir($dir))) {
            if (($file != '.') && ($file != '..')) {
                if (is_dir($src . '/' . $file)) {
                   self::copy_dir($src . '/' . $file, $dst . '/' . $file);
                } else {
                    copy($src . '/' . $file, $dst . '/' . $file);
                }
            }
        }
        closedir($dir);
    
        return true;
    }

    /**
     * 移动文件
     * @param string $source 源文件路径
     * @param string $destination 目标文件路径
     * @return bool
     */
    public static function recursiveMove($source, $destination) {

        // 判断是否为文件
        if(is_file($source)) {
            rename($source, $destination);

        } else {
            // 检查源目录是否存在
            if (!is_dir($source)) {
                return false;
            }

            // 若目标目录不存在，则创建它
            if (!is_dir($destination)) {
                self::mk_dir($destination);
            }

            // 打开源目录
            $dir = opendir($source);
            while (($file = readdir($dir)) !== false) {
                if ($file == '.' || $file == '..') {
                    continue;
                }

                $sourcePath = $source . '/' . $file;
                $destinationPath = $destination . '/' . $file;

                if (is_dir($sourcePath)) {
                    // 递归处理子目录
                    self::recursiveMove($sourcePath, $destinationPath);
                } else {
                    // 移动文件
                    rename($sourcePath, $destinationPath);
                }
            }
            closedir($dir);
        }

        return true;
    }

    /**
     * 批量压缩文件
     * @param array|string $files 需要压缩的文件数组|文件夹路径
     * @param string $zipfile 压缩包的文件路径
     * @param bool $isdownload 是否下载，默认为false
     * @return bool
     */
    public static function zip($files = [], $zipfile = '', $isdownload = false) {
        
        if(!$files) return self::setError('需压缩文件不能为空');
        if(!$zipfile) return self::setError('压缩包文件路径不能为空');

        $zip = new \ZipArchive();

        // 注意: 每次都是创建一个新的压缩包，如果之前存在，则直接覆盖
        if ($zip->open($zipfile, \ZIPARCHIVE::OVERWRITE | \ZIPARCHIVE::CREATE)!==TRUE) {
            return self::setError('无法打开文件，或者文件创建失败');
        }

        if(is_array($files)) { // 文件数组
            foreach($files as $val){
                if(file_exists($val)){
                    //addFile函数首个参数如果带有路径，则压缩的文件里包含的是带有路径的文件压缩
                    //若不希望带有路径，则需要该函数的第二个参数
                    $zip->addFile($val, basename($val));//第二个参数是放在压缩包中的文件名称，如果文件可能会有重复，就需要注意一下
                }
            }
        } else { // 文件夹路径
            $base_path = substr($files, 0, strrpos($files, '/')) . '/';
            $dir_name = substr(strrchr($files, '/'), 1);
            self::add_dir_file_zip($base_path, $dir_name, $zip);
        }

        $zip->close();//关闭

        if(!file_exists($zipfile)){
            return self::setError('无法找到文件'); //即使创建，仍有可能失败
        }

        if ($isdownload) {
            //需下载
            header("Cache-Control: public");
            header("Content-Description: File Transfer");
            header('Content-disposition: attachment; filename='.basename($zipfile)); //文件名
            header("Content-Type: application/zip"); //zip格式的
            header("Content-Transfer-Encoding: binary"); //告诉浏览器，这是二进制文件
            header('Content-Length: '. filesize($zipfile)); //告诉浏览器，文件大小
            @readfile($zipfile);
        }

        return true;
    }

    /**
     * 添加文件夹内的所有文件到压缩包
     * @param $basePath
     * @param $dirName
     * @param $zip
     * @param string[] $exclude_ext
     */
    static public function add_dir_file_zip($basePath, $dirName, $zip, $exclude_ext = ['zip'] )
    {
        $handler = opendir($basePath . $dirName);
        while (($filename = readdir($handler)) !== false) {
            if ($filename != '.' && $filename != '..') {
                if (is_dir($basePath . $dirName . '/' . $filename)) {
                    // 当前路径是文件夹
                    self::add_dir_file_zip($basePath, $dirName . '/' . $filename, $zip);
                } else {
                    if(!in_array(pathinfo($filename, PATHINFO_EXTENSION), $exclude_ext)) {
                        // 写入文件到压缩包
                        $zip->addFile($basePath . $dirName . '/' . $filename, $dirName . '/' . $filename);
                    }
                }
            }
        }
        closedir($handler);
    }

    /**
     * 解压zip文件到指定文件夹
     * @param string $zipfile 压缩文件路径
     * @param string $path 缩包解压到的目标路径
     * @return bool
     */
    public static function unzip($zipfile, $path)
    { 
        $zip = new \ZipArchive();

        if(!is_dir($path)) {
            mkdir($path, 0755, true);
        } 

        //打开zip文档，如果打开失败返回提示信息 
        if ($zip->open($zipfile) !== TRUE) {
            return self::setError('无法打开文件，或者文件创建失败');
        }

        //将压缩文件解压到指定的目录下 
        $result = $zip->extractTo($path); 
        //关闭zip文档 
        $zip->close();

        return $result;
    }

    /**
     * 获取所有的文件夹
     * @param $path
     * @return array
     */
    public static function get_all_folder($path) {
        $items = scandir($path);

        return $items;

        $sub_folders = [];
        foreach ($items as $item) {
            if (is_dir($path . "/" . $item) && $item != '.' && $item != '..') {
                $sub_folders[] = $item;
            }
        }
        return $sub_folders;
    }

    /**
     * 获取目录中所有文件或者文件夹（空）的路径
     * @param string $path 文件路径
     * @return array
     */
    public static function get_all_empty_dir_file($path)
    {   
        if(!is_dir($path)) return [$path];

        $child_dirs = scandir($path);
        
        $file_list = [];
        foreach($child_dirs as $child_dir){
            if($child_dir != '.' && $child_dir != '..'){//去掉当前目录和上层目录
                if(is_dir($path . "/" . $child_dir) && count(scandir($path . "/" . $child_dir)) > 2){//文件或者空文件夹
                    $file_list = array_merge($file_list,self::get_all_empty_dir_file($path . "/" . $child_dir));
                }else{
                    $file_list[] = $path . "/" . $child_dir;   
                }
            }
        }
        
        return $file_list;
    }

    /**
     * 获取目录中所有文件的路径
     * @param string $dir 文件路径
     * @param string $level 层级 1-1级 all-全部
     * @return array
     */
    public static function get_all_dir($dir,$level = 'all')
    {   
        if(is_dir($dir)){
            $files = array();
            $child_dirs = scandir($dir);
            foreach($child_dirs as $child_dir){
                //'.'和'..'是Linux系统中的当前目录和上一级目录，必须排除掉，  
                //否则会进入死循环，报segmentation falt 错误
                if($child_dir != '.' && $child_dir != '..'){
                    if($level == 'all'){
                        if(is_dir($dir.'/'.$child_dir)){
                            $files[$child_dir] = self::get_all_dir($dir.'/'.$child_dir);
                        }else{
                            $files[] = $child_dir;
                        }
                    }else{
                        $files[] = $child_dir;
                    }
                }
            }
            return $files;
        }else{
            return [];
        }
    }
	
}